package cn.com.zsw.gblog.utils;

import org.springframework.stereotype.Component;
import org.wltea.analyzer.core.IKSegmenter;
import org.wltea.analyzer.core.Lexeme;

import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @Author ：shiwangzhou
 * @Date ：Created in 2020-07-09 19:12
 * @Description： 文章概要工具类。
 */
@Component
public class SummaryUtils {

    /**
     * 句子得分使用方法
     */
    final Set<String> styleSet = new HashSet<String>();
    /**
     * 保留关键词数量
     */
    int n = 50;
    /**
     * 关键词间的距离阀值
     */
    int CLUSTER_THRESHOLD = 5;


    /**
     * 句子编号及分词列表
     */
    Map<Integer, List<String>> sentSegmentWords = null;

    private SummaryUtils() {
        styleSet.add("meanstd");
        styleSet.add("default");
        styleSet.add("MMR");
    }

    public static void main(String[] args) {

        SummaryUtils summary = new SummaryUtils();


        String text = "我国古代历史演义小说的代表作。明代小说家罗贯中依据有关三国的历史、杂记，在广泛吸取民间传说和民间艺人创作成果的基础上，加工、再创作了这部长篇章回小说。" +
                "作品写的是汉末到晋初这一历史时期魏、蜀、吴三个封建统治集团间政治、军事、外交等各方面的复杂斗争。通过这些描写，揭露了社会的黑暗与腐朽，谴责了统治阶级的残暴与奸诈，" +
                "反映了人民在动乱时代的苦难和明君仁政的愿望。小说也反映了作者对农民起义的偏见，以及因果报应和宿命论等思想。战争描写是《三国演义》突出的艺术成就。" +
                "这部小说通过惊心动魄的军事、政治斗争，运用夸张、对比、烘托、渲染等艺术手法，成功地塑造了诸葛亮、曹操、关羽、张飞等一批鲜明、生动的人物形象。" +
                "《三国演义》结构宏伟而又严密精巧，语言简洁、明快、生动。有的评论认为这部作品在艺术上的不足之处是人物性格缺乏发展变化，有的人物渲染夸张过分导致失真。" +
                "《三国演义》标志着历史演义小说的辉煌成就。在传播政治、军事斗争经验、推动历史演义创作的繁荣等方面都起过积极作用。" +
                "《三国演义》的版本主要有明嘉靖刻本《三国志通俗演义》和清毛宗岗增删评点的《三国志演义》";

        System.out.println("1summary: " + text);


        String keySentences = summary.SummaryMeanstdTxt(text);
        System.out.println("2summary: " + keySentences);


    }

    /**
     * @Author
     * @Description： 利用均值方差自动文摘
     * @Date
     */
    public String SummaryMeanstdTxt(String text) {
        //将文本拆分成句子列表
        List<String> sentencesList = this.SplitSentences(text);

        //利用IK分词组件将文本分词，返回分词列表
        List<String> words = this.IKSegment(text);
        //统计分词频率
        Map<String, Integer> wordsMap = new HashMap<String, Integer>();
        for (String word : words) {
            Integer val = wordsMap.get(word);
            wordsMap.put(word, val == null ? 1 : val + 1);

        }
        //使用优先队列自动排序
        Queue<Map.Entry<String, Integer>> wordsQueue = new PriorityQueue<Map.Entry<String, Integer>>(wordsMap.size(), new Comparator<Map.Entry<String, Integer>>() {
            @Override
            public int compare(Map.Entry<String, Integer> o1,
                               Map.Entry<String, Integer> o2) {
                return o2.getValue() - o1.getValue();
            }
        });
        wordsQueue.addAll(wordsMap.entrySet());

        if (n > wordsMap.size()) {
            n = wordsQueue.size();
        }

        //取前N个频次最高的词存在wordsList top-n关键词
        List<String> wordsList = new ArrayList<String>(n);
        for (int i = 0; i < n; i++) {
            Map.Entry<String, Integer> entry = wordsQueue.poll();
            wordsList.add(entry.getKey());
        }

        //利用频次关键字，给句子打分，并对打分后句子列表依据得分大小降序排序
        //返回的得分,从第一句开始,句子编号的自然顺序
        Map<Integer, Double> scoresLinkedMap = scoreSentences(sentencesList, wordsList);


        //approach1,利用均值和标准差过滤非重要句子
        Map<Integer, String> keySentence = new LinkedHashMap<Integer, String>();

        //句子得分均值
        double sentenceMean = 0.0;
        for (double value : scoresLinkedMap.values()) {
            sentenceMean += value;
        }
        sentenceMean /= scoresLinkedMap.size();

        //句子得分标准差
        double sentenceStd = 0.0;
        for (Double score : scoresLinkedMap.values()) {
            sentenceStd += Math.pow((score - sentenceMean), 2);
        }
        sentenceStd = Math.sqrt(sentenceStd / scoresLinkedMap.size());

        for (Map.Entry<Integer, Double> entry : scoresLinkedMap.entrySet()) {
            //过滤低分句子
            if (entry.getValue() > (sentenceMean + 0.5 * sentenceStd)) {
                keySentence.put(entry.getKey(), sentencesList.get(entry.getKey()));
            }
        }

        StringBuilder sb = new StringBuilder();
        for (int index : keySentence.keySet()) {
            sb.append(keySentence.get(index));
        }
        return sb.toString();
    }

    /**
     * @Author：
     * @Description： 利用正则将文本拆分成句子
     * @Date
     */
    private List<String> SplitSentences(String text) {
        List<String> sentences = new ArrayList<String>();
        String regEx = "[!?。！？.]";
        Pattern p = Pattern.compile(regEx);
        String[] sents = p.split(text);
        Matcher m = p.matcher(text);

        int sentsLen = sents.length;
        //把每个句子的末尾标点符号加上
        if (sentsLen > 0) {
            int index = 0;
            while (index < sentsLen) {
                if (m.find()) {
                    sents[index] += m.group();
                }
                index++;
            }
        }
        for (String sentence : sents) {
            //文章从网页上拷贝过来后遗留下来的没有处理掉的html的标志
            sentence = sentence.replaceAll("([”“—‘’·\"↓•])", "");
            sentences.add(sentence.trim());
        }
        return sentences;
    }

    /**
     * 每个句子得分  (keywordsLen*keywordsLen/totalWordsLen)
     *
     * @param sentences 分句
     * @param topnWords keywords top-n关键词
     * @return
     */
    private Map<Integer, Double> scoreSentences(List<String> sentences, List<String> topnWords) {
        //句子编号，得分
        Map<Integer, Double> scoresMap = new LinkedHashMap<>();
        sentSegmentWords = new HashMap<>();
        //句子编号
        int sentenceIdx = -1;
        for (String sentence : sentences) {
            sentenceIdx += 1;
            //对每个句子分词
            List<String> words = this.IKSegment(sentence);
            sentSegmentWords.put(sentenceIdx, words);
            //每个关词键在本句子中的位置
            List<Integer> wordIdx = new ArrayList<Integer>();
            for (String word : topnWords) {
                if (words.contains(word)) {
                    wordIdx.add(words.indexOf(word));
                }
            }
            if (wordIdx.size() == 0) {
                continue;
            }
            Collections.sort(wordIdx);
            //对于两个连续的单词，利用单词位置索引，通过距离阀值计算一个族
            List<List<Integer>> clusters = new ArrayList<List<Integer>>();//根据本句中的关键词的距离存放多个词族
            List<Integer> cluster = new ArrayList<Integer>();
            cluster.add(wordIdx.get(0));
            int i = 1;
            while (i < wordIdx.size()) {
                if ((wordIdx.get(i) - wordIdx.get(i - 1)) < this.CLUSTER_THRESHOLD) {
                    cluster.add(wordIdx.get(i));
                } else {
                    clusters.add(cluster);
                    cluster = new ArrayList<Integer>();
                    cluster.add(wordIdx.get(i));
                }
                i += 1;
            }
            clusters.add(cluster);
            //对每个词族打分，选择最高得分作为本句的得分
            double maxClusterScore = 0.0;
            for (List<Integer> clu : clusters) {
                //关键词个数
                int keywordsLen = clu.size();
                //总的词数
                int totalWordsLen = clu.get(keywordsLen - 1) - clu.get(0) + 1;
                double score = 1.0 * keywordsLen * keywordsLen / totalWordsLen;
                if (score > maxClusterScore) {
                    maxClusterScore = score;
                }
            }
            scoresMap.put(sentenceIdx, maxClusterScore);
        }
        return scoresMap;
    }

    /**
     * 这里使用IK进行分词
     *
     * @param text
     * @return
     */
    private List<String> IKSegment(String text) {
        List<String> wordList = new ArrayList<String>();
        Reader reader = new StringReader(text);
        IKSegmenter ik = new IKSegmenter(reader, true);
        Lexeme lex = null;
        try {
            while ((lex = ik.next()) != null) {
                String word = lex.getLexemeText();
                if (word.equals("nbsp")) {
//                if (word.equals("nbsp") || this.stopWords.contains(word)) {
                    continue;
                }
                if (word.length() > 1 && word != "\t") {
                    wordList.add(word);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return wordList;
    }

}
